
// TODO: Process the read in skin names and normalise them to the path of the model.

#include "nAnachronox/nmd2model.h"
#include "kernel/nkernelserver.h"
#include "kernel/nfile.h"

float norm2048[2048][3] = {
#include "nAnachronox/norm2048.h"
};

nNebulaClass(nMD2Model, "nresource");

//------------------------------------------------------------------------------
/**
*/
nMD2Model::nMD2Model() :
	// variable initialisation
    numVertices(0),
	numPrimitives(0),
	numFrames(0),
	numSurfaces(0),
	surfaces(0),
	skinNames(0),
	frames(0)
{
    // empty
}

//------------------------------------------------------------------------------
/**
*/
nMD2Model::~nMD2Model()
{
    if (this->IsValid())
    {
        this->Unload();
    }
}

//------------------------------------------------------------------------------
/**
    Unload everything.
*/
void
nMD2Model::UnloadResource()
{
    n_assert(this->IsValid());

	if (this->frames) {
		for (int i=0; i < this->numFrames; i += 1) {
			n_delete(this->frames[i].name);
			this->frames[i].name = 0;

			n_free(this->frames[i].vertices);		
		}

		n_free(this->frames);
	}

	for (int i = 0; i < this->numPrimitives; i += 1)
		n_free(this->primitives[i].vertexInfo);		
	n_free(this->primitives);

	if (this->skinNames) {
		n_free(this->skinNames);
	}

	if (this->surfaces) {
		n_free(this->surfaces);
	}

	this->SetValid(false);
}

float
nMD2Model::CalculateScalar(int v) {
    int measure = 2, newMeasure;
    int limit = 1000000;
	while (measure < limit) {
        newMeasure = measure * 2;
		if (v == measure)
            return float(v);
		else if (v < newMeasure)
            return float(newMeasure);
        measure = newMeasure;
	}
	return float(measure);
}

//------------------------------------------------------------------------------
/**
    This method is either called directly from the nResource::Load() method
    (in synchronous mode), or from the loader thread (in asynchronous mode).
    The method must try to validate its resources, set the valid and pending
    flags, and return a success code.
    This method may be called from a thread.
*/
bool
nMD2Model::LoadResource()
{
    n_assert(!this->IsValid());

    bool success = true;

	nFileServer2 *fileServer = kernelServer->GetFileServer();
    nFile* fileOb = fileServer->NewFileObject();

    n_assert(fileOb);

    // open the file
    if (!fileOb->Open(this->GetFilename().Get(), "r"))
    {
        n_printf("nMD2Model: could not open file '%s'!\n", this->GetFilename().Get());
        fileOb->Release();
        return false;
    }

	MD2Header md2Header;
	MultipleSurfaceHeader msHeader;
	TLODData lodData;
	TaggedSurfaceHeader tsHeader;

	fileOb->Read(&md2Header, sizeof(md2Header));

	float pow2;
	pow2 = this->CalculateScalar(md2Header.skinWidth);
	if (pow2 == md2Header.skinWidth)
		this->uScalar = 1.0f;
	else
		this->uScalar = md2Header.skinWidth / pow2;
	pow2 = this->CalculateScalar(md2Header.skinHeight);
	if (pow2 == md2Header.skinHeight)
		this->vScalar = 1.0f;
	else
		this->vScalar = md2Header.skinHeight / pow2;

	int ident = 'I' + ('D' << 8) + ('P' << 16) + ('2' << 24);
	if (md2Header.ident != ident) {
        n_printf("nMD2Model: file '%s' had invalid id %c%c%c%c !\n", this->GetFilename().Get(), ident, ident >> 8, ident >> 16, ident >> 24);
		fileOb->Close();
        fileOb->Release();
        return false;
	}

	fileOb->Read(&msHeader, sizeof(msHeader));
	fileOb->Read(&lodData, sizeof(lodData));
	fileOb->Read(&tsHeader, sizeof(tsHeader));

	this->numSurfaces = msHeader.numPrims;

	this->surfaces = (MultipleSurface*)n_malloc(sizeof(MultipleSurface) * this->numSurfaces);
	fileOb->Seek(msHeader.ofsPrims, nFile::nSeekType::START);
	fileOb->Read(this->surfaces, sizeof(MultipleSurface) * msHeader.numPrims);

	this->skinNames = (SkinName *)n_malloc(sizeof(SkinName) * md2Header.numSkins);
	fileOb->Seek(md2Header.ofsSkins, nFile::nSeekType::START);
	fileOb->Read(this->skinNames, sizeof(SkinName) * md2Header.numSkins);

	int headerSize, vertexSize;
	if (md2Header.vertexres == 0) {
		headerSize = sizeof(FrameHeader3);
		vertexSize = sizeof(FrameVertexData3);
        this->numVertices = (md2Header.frameSize - headerSize + vertexSize) / vertexSize;
	} else if (md2Header.vertexres == 1) {
		headerSize = sizeof(FrameHeader4);
		vertexSize = sizeof(FrameVertexData4);
        this->numVertices = (md2Header.frameSize - headerSize + vertexSize) / vertexSize;
	} else if (md2Header.vertexres == 2) {
		headerSize = sizeof(FrameHeader6);
		vertexSize = sizeof(FrameVertexData6);
        this->numVertices = (md2Header.frameSize - headerSize + vertexSize) / vertexSize;
	}

	this->numFrames = md2Header.numFrames;

	// Count the number of primitives in each surface to get the total count.
	this->numPrimitives = 0;
	for (int i=0; i < this->numSurfaces; i+=1)
		this->numPrimitives += this->surfaces[i].numPrimitives;

	// Use that count to allocate the space needed to store the read in primitives.
	this->primitives = (Primitive *)n_malloc(sizeof(Primitive) * this->numPrimitives)

	// Now parse the primitives out of the file.
	Primitive *p;
	int numVertices, numPrimitives = 0;
	fileOb->Seek(md2Header.ofsGLCmds, nFile::nSeekType::START);
	for (int i = 0; i < this->numPrimitives; i += 1) {
		p = &this->primitives[i];
		fileOb->Read(&numVertices, sizeof(int));

		// If we get a vertices entry which is 0, then we have hit the end of the list.
		if (numVertices == 0) {
			n_error("Primitive parsing found unexpected end at %d of %d.\n", i, this->numPrimitives);
			break;
		}

		// Otherwise, read in the current primitive.
		numPrimitives += 1;
		if (numVertices > 0) {
			p->primitiveType = nGfxServer2::PrimitiveType::TriangleStrip;
		} else {
			p->primitiveType = nGfxServer2::PrimitiveType::TriangleFan;
			numVertices = -numVertices;
		}

		p->numVertices = numVertices;
		p->vertexInfo = (PrimitiveVertex *)n_malloc(sizeof(PrimitiveVertex) * numVertices)
		for (int j = 0; j < numVertices; j += 1)
			fileOb->Read(&p->vertexInfo[j], sizeof(PrimitiveVertex));
	}

	// TODO: Sanity check, make sure that numPrimitives == this->numPrimitives.

	this->frames = (AnimationFrame *)n_malloc(this->numFrames * sizeof(AnimationFrame));

	// Read in the frames out of the file.
	fileOb->Seek(md2Header.ofsFrames, nFile::nSeekType::START);

	int shift[3] = { 0, 11, 21 };
	int mask[3] = { 0x000007ff, 0x000003ff, 0x000007ff };
	float max[3] = { 2047.0, 1023.0, 2047.0 };

	FrameHeader3 *fh3;
	FrameHeader4 *fh4;
	FrameHeader6 *fh6;
	Vertex *nVertex;
	AnimationFrame *frame;

	if (md2Header.vertexres == 0) {
		fh3 = (FrameHeader3 *)n_malloc(md2Header.frameSize);
	} else if (md2Header.vertexres == 1) {
		fh4 = (FrameHeader4 *)n_malloc(md2Header.frameSize);
	} else if (md2Header.vertexres == 2)
		fh6 = (FrameHeader6 *)n_malloc(md2Header.frameSize);

	// Extract a triangle list for each surface in each frame.
	for (int i = 0; i < this->numFrames; i += 1) {
		frame = &this->frames[i];
		frame->name = (nString *)n_new(nString);
		frame->vertices = (Vertex *)n_malloc(this->numVertices * sizeof(Vertex));

		if (md2Header.vertexres == 0) {
			fileOb->Read(fh3, md2Header.frameSize);
			frame->name->Set(fh3->name);
		} else if (md2Header.vertexres == 1) {
			fileOb->Read(fh4, md2Header.frameSize);
			frame->name->Set(fh4->name);
		} else if (md2Header.vertexres == 2) {
			fileOb->Read(fh6, md2Header.frameSize);
			frame->name->Set(fh6->name);
		}

		for (int j = 0; j < this->numVertices; j += 1) {
			nVertex = &frame->vertices[j];

			if (md2Header.vertexres == 0) {
				nVertex->idxNormal = fh3->verts[j].idxNormal;
			} else if (md2Header.vertexres == 1) {
				nVertex->idxNormal = fh4->verts[j].idxNormal;
			} else if (md2Header.vertexres == 2)
				nVertex->idxNormal = fh6->verts[j].idxNormal;

			for (int k=0; k < 3; k += 1)
				if (md2Header.vertexres == 0) {
					nVertex->xyz[k] = fh3->verts[j].v[k] * fh3->scaleVector[k] + fh3->translationVector[k];
				} else if (md2Header.vertexres == 1) {
					nVertex->xyz[k] = ((fh4->verts[j].v >> shift[k]) & mask[k]) * fh4->scaleVector[k] + fh4->translationVector[k];
				} else if (md2Header.vertexres == 2)
					nVertex->xyz[k] = fh6->verts[j].v[k] * fh6->scaleVector[k] + fh6->translationVector[k];
		}
	}

	// Free the various temporarily allocated blocks of memory.
	if (md2Header.vertexres == 0) {
		n_free(fh3);
	} else if (md2Header.vertexres == 1) {
		n_free(fh4);
	} else if (md2Header.vertexres == 2)
		n_free(fh6);

	fileOb->Close();
	fileOb->Release();

	this->SetValid(success);
    return success;
}

//------------------------------------------------------------------------------
bool
nMD2Model::PopulateMesh(nDynamicMesh *dynMesh, int surfaceIdx)
{
    float *dstVertices = 0;
    int maxVertices = 0;
	int curVertex = 0;
    int curIndex  = 0;

	dynMesh->Begin(dstVertices, maxVertices);

	int numPrimitives;
	int upper, lower, primitivesUsed = 0;
	Primitive primitive;
	PrimitiveVertex vi0, vi1, vi2;
	Vertex verts;
	AnimationFrame frame = this->frames[173];

	float uScalar = 1.0f;
	float vScalar = 1.0f;

	if (surfaceIdx == 0) {
		uScalar = this->uScalar;
		vScalar = this->vScalar;
	}

	for (int j = 0; j < this->numSurfaces; j+=1) {
		numPrimitives = this->surfaces[j].numPrimitives;
		if (j == surfaceIdx) {
			lower = primitivesUsed;
			upper = lower + numPrimitives;
			for (int k = lower; k < upper; k++) {
				primitive = primitives[k];
				for (int l = 2; l < primitive.numVertices; l++) {
		            curVertex += 3;

					if (l % 2) {
						vi0 = primitive.vertexInfo[l-2];
						vi1 = primitive.vertexInfo[l-1];
						vi2 = primitive.vertexInfo[l-0];
					} else {
						vi0 = primitive.vertexInfo[l-0];
						vi1 = primitive.vertexInfo[l-1];
						vi2 = primitive.vertexInfo[l-2];
					}

					verts = frame.vertices[vi0.idxVertex];
					dstVertices[curIndex++] = verts.xyz[0];
					dstVertices[curIndex++] = verts.xyz[1];
					dstVertices[curIndex++] = verts.xyz[2];

					dstVertices[curIndex++] = norm2048[verts.idxNormal][0];
					dstVertices[curIndex++] = norm2048[verts.idxNormal][1];
					dstVertices[curIndex++] = norm2048[verts.idxNormal][2];

					dstVertices[curIndex++] = vi0.u * uScalar;
					dstVertices[curIndex++] = vi0.v * vScalar;

					verts = frame.vertices[vi1.idxVertex];
					dstVertices[curIndex++] = verts.xyz[0];
					dstVertices[curIndex++] = verts.xyz[1];
					dstVertices[curIndex++] = verts.xyz[2];

					dstVertices[curIndex++] = norm2048[verts.idxNormal][0];
					dstVertices[curIndex++] = norm2048[verts.idxNormal][1];
					dstVertices[curIndex++] = norm2048[verts.idxNormal][2];

					dstVertices[curIndex++] = vi1.u * uScalar;
					dstVertices[curIndex++] = vi1.v * vScalar;

					verts = frame.vertices[vi2.idxVertex];
					dstVertices[curIndex++] = verts.xyz[0];
					dstVertices[curIndex++] = verts.xyz[1];
					dstVertices[curIndex++] = verts.xyz[2];

					dstVertices[curIndex++] = norm2048[verts.idxNormal][0];
					dstVertices[curIndex++] = norm2048[verts.idxNormal][1];
					dstVertices[curIndex++] = norm2048[verts.idxNormal][2];

					dstVertices[curIndex++] = vi2.u * uScalar;
					dstVertices[curIndex++] = vi2.v * vScalar;

					if (curVertex > maxVertices-3) {
						dynMesh->Swap(curVertex, dstVertices);
						curVertex = 0;
						curIndex = 0;
					}
				}
			}
		}

		primitivesUsed += numPrimitives;
	}

	dynMesh->End(curVertex);

	return true;
}

